package com.autonavi.yunda.yunji.core.service;

import com.autonavi.yunda.yunji.common.enums.EditType;
import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.core.engine.Engine;
import com.autonavi.yunda.yunji.core.engine.argresolver.ParamResolver;
import com.autonavi.yunda.yunji.core.engine.script.GroovyFunctionScriptClass;
import com.autonavi.yunda.yunji.core.engine.script.IFunctionScript;
import com.autonavi.yunda.yunji.core.engine.script.InterfaceScriptClass;
import com.autonavi.yunda.yunji.core.engine.script.ScriptClassManager;
import com.autonavi.yunda.yunji.core.engine.scriptresolver.ScriptResolver;
import com.autonavi.yunda.yunji.core.enums.NodeType;
import com.autonavi.yunda.yunji.core.model.dto.InterfaceConfigDTO;
import com.autonavi.yunda.yunji.core.model.dto.ProjectDTO;
import com.autonavi.yunda.yunji.core.model.dto.TaskConfigDTO;
import com.autonavi.yunda.yunji.core.utils.DynamicConfigUtil;
import com.autonavi.yunda.yunji.core.vo.DebugResult;
import com.autonavi.yunda.yunji.core.vo.EngineInterfaceConfigVO;
import com.autonavi.yunda.yunji.core.vo.HttpRequest;
import com.autonavi.yunda.yunji.core.vo.ProjectExtraData;
import com.autonavi.yunda.yunji.core.vo.node.BaseNode;
import com.autonavi.yunda.yunji.core.vo.node.GroovyFunctionNode;
import com.autonavi.yunda.yunji.core.vo.node.JsFunctionNode;
import com.autonavi.yunda.yunji.core.vo.node.SqlNode;
import com.autonavi.yunda.yunji.core.vo.param.NodeParam;
import com.autonavi.yunda.yunji.core.vo.response.SingleNodeDebugResultVO;
import groovy.lang.GString;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.autonavi.yunda.yunji.core.config.EngineConstant.*;

/**
 * @author cizhong.wcs
 * {@code @date} 2022/5/24 17:19
 */
@Slf4j
@Component
public class DebugService {
    @Resource
    Engine engine;
    @Resource
    InterfaceConfigService interfaceConfigService;
    @Resource
    ProjectService projectService;
    @Resource
    TaskConfigService taskConfigService;

    public SingleNodeDebugResultVO debugNodeWithDetail(BaseNode node, String projectId) {
        // 解析参数
        Map<String, Object> debugParamMap = parseNodeParams(node, projectId);
        Object result;
        String script;
        SingleNodeDebugResultVO debugResultVO = new SingleNodeDebugResultVO();
        IFunctionScript functionScript;
        if (NodeType.groovy.equals(node.nodeType)) {
            debugResultVO.setScript(((GroovyFunctionNode) node).script);
        }
        if (NodeType.javascript.equals(node.nodeType)) {
            debugResultVO.setScript(((JsFunctionNode) node).script);
        }
        try {
            node.setSingleNodeDebug(true);
            // 解析节点
            functionScript = ScriptResolver.resolveNode(node);
            script = functionScript.getScript();
            debugResultVO.setScript(script);
        } catch (Throwable e) {
            Throwable throwable = e;
            if (throwable.getCause() instanceof MultipleCompilationErrorsException) {
                throwable = throwable.getCause();
            }
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            throwable.printStackTrace(new PrintStream(outputStream));
            debugResultVO.setErrorMessage(outputStream.toString());
            return debugResultVO;
        }
        Object instance = null;
        try {
            if (NodeType.groovy.equals(node.nodeType)) {
                GroovyFunctionScriptClass groovyScript = (GroovyFunctionScriptClass) functionScript;
                instance = groovyScript.newInstance();
                result = groovyScript.exeFun(instance, debugParamMap, EXEC_PROCESS);
            } else {
                result = functionScript.exeFun(debugParamMap, EXEC_PROCESS);
            }
            debugResultVO.setResult(result);
        } catch (Exception e) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            e.printStackTrace(new PrintStream(outputStream));
            debugResultVO.setErrorMessage(outputStream.toString());
        }
        if (instance != null) {
            Object logInfo = ((GroovyFunctionScriptClass) functionScript).exeFun(instance, null, EXEC_GET_PROPERTY_PROCESS, DEBUG_LOG_PARAM_NAME);
            debugResultVO.setDebugLog(logInfo.toString());
        }
        return debugResultVO;
    }

    public Object debugNode(BaseNode node, String projectId) {
        // 解析映射参数
        Map<String, Object> debugParamMap = parseNodeParams(node, projectId);
        // 解析脚本
        IFunctionScript functionScript = ScriptResolver.resolveNode(node);
        // 执行process方法
        Object result = functionScript.exeFun(debugParamMap, EXEC_PROCESS);
        if (result instanceof GString) {
            result = result.toString();
        }
        if (!node.resultValidators.isEmpty()) {
            //如果是预览sql和行数，则不进行结果校验
            if (!(node instanceof SqlNode && ((SqlNode) node).debug.isDebug())) {
                functionScript.exeFun(null, EXEC_VALIDATOR_RESULT_PROCESS, result);
            }
        }
        if (NodeType.edge.equals(node.getNodeType()) && !(result instanceof Boolean)) {
            throw new AmapException(AmapExceptionCode.COMMON_ILLEGAL_PARAM, "条件脚本回必须为true或者false,当前result = " + JsonUtils.toString(result));
        }
        return result;
    }

    /**
     * 解析参数
     * 将节点中的参数集合转为key-value的map映射
     * 注意如果节点是transform类型，有做特殊处理
     * @param node
     * @param projectId
     * @return
     */
    private Map<String, Object> parseNodeParams(BaseNode node, String projectId) {
        if (node.nodeType.isSpecial()) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_WARN, "不支持调试空节点");
        }
        Map<String, Object> debugParamMap = new HashMap<>(8);
        for (NodeParam nodeParam : node.params) {
            // 校验参数
            nodeParam.checkParamWhenDebug();
            Object debugValue = nodeParam.debugValue;
            // 参数为动态数据源的处理方式
            if (DYNAMIC_CONFIG_REF.equals(nodeParam.dataSource)) {
                debugValue = ParamResolver.getParamValueByType(DynamicConfigUtil.getString(projectId, Objects.requireNonNull(debugValue).toString()), nodeParam.type);
            }
            // 否则当普通变量处理
            else {
                debugValue = ParamResolver.getParamValueByType(debugValue, nodeParam.type);
            }
            // 如果类型是String，并且是null，需要判断是否当空串处理
            // 如果类型是String，并且是空串，看看是否当null处理
            if("string".equals(nodeParam.type)) {
                if(null == nodeParam.debugValue && nodeParam.emptyFlag) {
                    debugValue = "";
                }else if("".equals(nodeParam.debugValue) && !nodeParam.emptyFlag) {
                    debugValue = null;
                }
            }
            debugParamMap.put(nodeParam.getParamNameInScript(), debugValue);
        }
        //transform 节点参数特殊操作
        if (node.getNodeType().equals(NodeType.transform)) {
            debugParamMap.put(SCRIPT_PARAMS_MAP_NAME, new HashMap<>(debugParamMap));
        }
        log.debug("debugNode debugParams = {}", JsonUtils.toString(debugParamMap.toString()));
        return debugParamMap;
    }

    public DebugResult debugAllNode(Long id, HttpRequest httpRequest) {
        return debugAllNode(id, EditType.INTERFACE.name(), httpRequest);
    }

    public DebugResult debugAllNode(Long id, String editType, HttpRequest httpRequest) {
        if (EditType.TASK.name().equalsIgnoreCase(editType)) {
            return debugTaskAllNode(taskConfigService.findByIdOrThrow(id), httpRequest);
        } else {
            InterfaceConfigDTO dto = interfaceConfigService.findByIdOrThrow(id);
            DebugResult debugResult = debugAllNode(dto, httpRequest);
            if (DEBUG_EXEC_SUCCESS.equals(debugResult.executeStatus)) {
                interfaceConfigService.updateComponentIdList(id);
            }
            return debugResult;
        }
    }

    public DebugResult debugTaskAllNode(TaskConfigDTO dto, HttpRequest httpRequest) {
        EngineInterfaceConfigVO vo = new EngineInterfaceConfigVO();
        BeanUtils.copyProperties(dto, vo);
        return debugAllNode(vo, httpRequest);
    }

    public DebugResult debugAllNode(InterfaceConfigDTO dto, HttpRequest httpRequest) {
        EngineInterfaceConfigVO vo = new EngineInterfaceConfigVO();
        BeanUtils.copyProperties(dto, vo);
        return debugAllNode(vo, httpRequest);
    }

    public DebugResult debugAllNode(EngineInterfaceConfigVO vo, HttpRequest httpRequest) {
        ProjectDTO project = projectService.findByProjectId(vo.getProjectId());
        ProjectExtraData projectExtraData = (project != null && project.getExtraData() != null) ? project.getExtraData() : new ProjectExtraData();
        vo.setProjectExtraData(projectExtraData);
        // 生成脚本文件
        InterfaceScriptClass scriptClass = ScriptClassManager.generateInterfaceScriptClass(vo);
        // 执行debug
        return engine.debugApi(scriptClass, httpRequest);
    }

}
